module objects.trianglemesh;

import material.material;
import std.stream;
import utils.vector2;
import utils.vector3;
import objects.triangle;
import objects.abstractobject;
import std.stdio;
import std.c.stdio;

class TriangleMesh: AbstractObject {
    protected {
        Triangle[] faces;
        TriangleNorms[] mNorms;
        TriangleTex[] faceTex;
        Material[] mats;
        int[] mTriMats;
        bool mHasIntNormals = false;
        char[] mNormsSaveTo;
    }

    this(Stream rawFile) {
        //3 vertices pro face
        //1 pointcoord (XYZ) + 1 texcoord (UV) pro vertex
        int numFaces = rawFile.size / (Triangle.sizeof+TriangleTex.sizeof);

        faces = new Triangle[numFaces];
        rawFile.readBlock(faces.ptr, numFaces*Triangle.sizeof);

        faceTex = new TriangleTex[numFaces];
        rawFile.readBlock(faceTex.ptr, numFaces*TriangleTex.sizeof);

        mTriMats.length = faces.length;
    }

    this() {
        //dummy
    }

    public void readNormalFile(char[] filename) {
        try {
            File normFile = new File(filename);
            scope(exit) normFile.close();
            mNorms = new TriangleNorms[faces.length];
            normFile.readBlock(mNorms.ptr, faces.length*Triangle.sizeof);
            mHasIntNormals = true;
        } catch (Exception e) {
            //no normals, will be interpolated
            mNormsSaveTo = filename;
        }
    }

    public void readMaterialFile(char[] filename) {
        try {
            File matFile = new File(filename);
            scope(exit) matFile.close();
            mTriMats = new int[faces.length];
            matFile.readBlock(mTriMats.ptr, faces.length*int.sizeof);
        } catch (Exception e) {
            //no material file
        }
    }

    public Triangle[] getTriangles() {
        return faces;
    }

    public Vector2f[] getTexCoords(int triIdx) {
        return faceTex[triIdx].texCoords;
    }

    public TriangleNorms getTriNormals(int triIdx, bool interpolate) {
        if (interpolate) {
            if (!mHasIntNormals)
                precalcNormals();
            return mNorms[triIdx];
        } else {
            Vector3f n = faces[triIdx].normal();
            return TriangleNorms(n,n,n);
        }
    }

    ///returns all triangles that share the point v
    ///this is sloooooooooooow
    private Triangle[] getNeighbors(Vector3f v) {
        Triangle[] res;
        foreach (inout Triangle t; faces) {
            foreach (inout Vector3f triVert; t.v) {
                if (triVert == v) {
                    res ~= t;
                    break;
                }
            }
        }
        return res;
    }

    ///calculate the normal of each vertex by interpolating from adjacent
    ///triangles
    ///looks stupid, but the tutor made us do it...
    private void precalcNormals() {
        mNorms = new TriangleNorms[faces.length];
        //hashmap to speed things up
        Vector3f[Vector3f] tmpNorm;
        foreach (int nFace, inout Triangle t; faces) {
            if (((1000*nFace)/faces.length) % 10 == 0)
                writef("Precalculating interpolated normals: %d%%  \r",(100*nFace)/faces.length);
            fflush(stdout);
            TriangleNorms norms;
            for (int i = 0; i < 3; i++) {
                //weighted sum over normals of all adjacent triangles
                //weight = distance to median point
                if (!(t.v[i] in tmpNorm)) {
                    //only calculate if not already in hashmap
                    Triangle[] nb = getNeighbors(t.v[i]);
                    float weightSum = 0;
                    foreach (inout Triangle nTri; nb) {
                        float d = (nTri.medianPoint - t.v[i]).length;
                        norms.n[i] += d*nTri.normal();
                        weightSum += d;
                    }
                    //at least the current triangle has to match
                    assert(weightSum != 0);
                    //debug writefln("%f",weightSum);
                    norms.n[i] /= weightSum;
                    tmpNorm[t.v[i]] = norms.n[i];
                } else {   //hashmap entry found
                    norms.n[i] = tmpNorm[t.v[i]];
                }
            }
            mNorms[nFace] = norms;
        }
        writefln("Precalculating interpolated normals: Done");
        mHasIntNormals = true;
        if (mNormsSaveTo.length > 0) {
            File normFile = new File(mNormsSaveTo,FileMode.OutNew);
            scope(exit) normFile.close();
            normFile.writeBlock(mNorms.ptr, mNorms.length*Triangle.sizeof);
        }
    }

    public void setMaterial(Material mat, int idx = 0) {
        std.stdio.writefln("Set material %d",idx);
        if (idx >= mats.length) {
            mats.length = idx+1;
        }
        mats[idx] = mat;
    }

    public Material getMaterial(int idx = 0) {
        if (idx >= mats.length) {
            return null;
        }
        return mats[idx];
    }

    public Material getTriMaterial(int triIdx) {
        int matIdx = mTriMats[triIdx];
        if (mats.length > matIdx)
            return mats[matIdx];
        else
            return null;
    }
}
